<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Font Editor</title>
<style>
    * {
        box-sizing: border-box;
    }
    /*
accent: rgb(26, 70, 102);
*/

    html,
    body {
        width: 100%;
        height: 100%;
        overflow: hidden;
    }

    body {
        font: 13px helvetica, arial, sans-serif;
        display: flex;
        flex-direction: column;
        background: rgb(24, 29, 32);
        margin: 0;
        padding: 0;
        color: #77c;
    }

    header {
        /*background: #88f;*/
        /*background-image: linear-gradient(#88f, #7f7fdf);*/
        border-bottom: 1px solid #333366;
        height: 30px;
        width: 100%;
    }

    header h1 {
        margin: 0;
        padding: 0 10px;
        line-height: 30px;
        font-size: 18px;
    }

    header h1 small {
        font-size: inherit;
        opacity: 0.7;
    }

    header h1 a {
        color: inherit;
        text-decoration: none;
        border-bottom: 1px solid #88d;
    }

    header #download-font {
        float: right;
        margin: 4px;
        border: 1px solid #88d;
        color: #88d;
        background: inherit;
        border-radius: 2px;
        font-size: 12px;
        padding: 2px 5px;
        outline: none;
    }

    header #download-font:hover {
        background: #336;
        color: #ccc;
    }

    #wrap {
        flex: 1;
        display: flex;
    }

    #glyph-list {
        width: 220px;
        overflow-y: scroll;
        overflow-x: hidden;
    }

    #glyph-list svg {
        display: inline-block;
        width: 90px;
        height: 90px;
    }

    #glyph-list svg.active {
        background: rgb(26, 70, 102);
    }

    #editor {
        flex: 1;
        display: flex;
        flex-direction: column;
        position: relative;
    }

    #editor #c {
        flex: 1;
        border-bottom: 1px solid #333366;
    }

    #buttons {
        position: absolute;
        top: 35px;
        left: 225px;
    }

    #buttons button {
        border: 1px solid #558;
        font-weight: bold;
        background: rgb(24, 29, 32);
        color: #558;
        font-size: 12px;
        outline: none;
        border-radius: 2px;
        margin-right: 10px;
    }

    #buttons span {
        color: #558;
    }

    #editor #preview-text-field {
        height: 30px;
        line-height: 30px;
        background: inherit;
        border: none;
        border-bottom: 1px solid #333366;
        padding: 0 10px;
        color: #88f;
        outline: none;
    }

    #editor #preview {
        height: 100px;
    }

    #code {
        position: absolute;
        bottom: 0;
        right: 0;
        height: 100px;
        overflow: hidden;
        background: inherit;
        border: none;
        border-left: 1px solid #336;
        color: #88f;
        outline: none;
    }
</style>

<header>
    <a id="download-font" download="generated.otf">Download font (.OTF)</a>
    <h1>Experimental font editor</h1>
</header>
<div id="wrap">
    <div id="glyph-list"></div>
    <div id="editor">
        <canvas id="c"></canvas>
        <input type="text" id="preview-text-field" value="HAMBURGEVONS">
        <canvas id="preview">
    </div>
</div>

<div id="buttons">
    <button id="add-point" title="Add a point to the end of the path">+ Add Point</button>
    <button id="remove-point" title="Remove the last point of the path">- Remove Last Point</button>
    <span>Shift-click a point to remove it</span>
</div>

<textarea id="code"></textarea>
<script type="module">
import * as opentype from "/dist/opentype.mjs";

function linePoint(t, x1, y1, x2, y2) {
    return [x1 + t * (x2 - x1),
        y1 + t * (y2 - y1)];
}

var SCREEN_SCALE = 20;
var TTF_SCALE = 80;

var gGlyphMap = {
    'A': [[0, 0], [4, 10], [8, 0]],
    'B': [[0, 0], [0, 10], [5, 10], [5, 5], [7, 5], [7, 0]],
    'C': [[0, 0], [0, 10], [8, 10], [8, 6], [3, 6], [3, 4], [8, 4], [8, 0]],
    'D': [[0, 0], [0, 3], [2, 3], [2, 7], [0, 7], [0, 10], [8, 10], [8, 0]],
    'E': [[0, 0], [0, 3], [0, 10], [8, 10], [8, 7], [3, 7], [3, 6], [5, 6], [5, 4], [3, 4], [3, 3], [8, 3], [8, 0]],
    'F': [[0, 0], [0, 3], [0, 10], [8, 10], [8, 7], [3, 7], [3, 6], [5, 6], [5, 3], [3, 3], [3, 0]],
    'G': [[0, 0], [0, 3], [0, 10], [8, 10], [8, 7], [3, 7], [3, 3], [5, 3], [5, 5], [8, 5], [8, 0]],
    'H': [[0, 0], [0, 3], [0, 7], [0, 10], [3, 10], [3, 7], [5, 7], [5, 10], [8, 10], [8, 0], [5, 0], [5, 3], [3, 3], [3, 0]],
    'I': [[0, 0], [0, 10], [3, 10], [3, 0]],
    'J': [[0, 0], [0, 3], [4, 3], [4, 10], [7, 10], [7, 0]],
    'K': [[0, 0], [0, 10], [3, 10], [3, 7], [5, 10], [8, 10], [5, 5], [8, 0], [5, 0], [3, 3], [3, 0]],
    'L': [[0, 0], [0, 10], [3, 10], [3, 3], [7, 3], [7, 0]],
    'M': [[0, 0], [0, 10], [3, 10], [4, 8], [5, 10], [8, 10], [8, 0], [5, 0], [5, 4], [4, 3], [3, 4], [3, 0]],
    'N': [[0, 0], [0, 10], [3, 10], [3, 9], [5, 7], [5, 10], [8, 10], [8, 0], [5, 0], [5, 3], [3, 5], [3, 0]],
    'O': [[0, 0], [0, 10], [7, 10], [7, 0]],
    'P': [[0, 0], [0, 10], [7, 10], [7, 4], [3, 4], [3, 0]],
    'Q': [[0, 0], [0, 10], [7, 10], [7, 0], [6, 0], [7, -1], [4, -1], [3, 0]],
    'R': [[0, 0], [0, 10], [7, 10], [7, 4], [5, 4], [7, 0], [4, 0], [3, 2], [3, 0]],
    'S': [[7, 10], [7, 7], [3, 7], [3, 6], [7, 6], [7, 0], [0, 0], [0, 3], [4, 3], [4, 4], [0, 4], [0, 10]],
    'T': [[2, 0], [2, 7], [0, 7], [0, 10], [7, 10], [7, 7], [5, 7], [5, 0]],
    'U': [[0, 0], [0, 10], [3, 10], [3, 3], [5, 3], [5, 10], [8, 10], [8, 0]],
    'V': [[0, 10], [3, 10], [4, 6], [5, 10], [8, 10], [5, 0], [3, 0]],
    'W': [[0, 10], [3, 10], [4, 6], [5, 8], [6, 6], [7, 10], [10, 10], [8, 0], [6, 0], [5, 3], [4, 0], [2, 0]],
    'X': [[0, 10], [3, 10], [4, 8], [5, 10], [8, 10], [5, 5], [8, 0], [5, 0], [4, 2], [3, 0], [0, 0], [3, 5]],
    'Y': [[0, 10], [3, 10], [4, 8], [5, 10], [8, 10], [5, 5], [5, 0], [3, 0], [3, 5]],
    'Z': [[0, 10], [7, 10], [7, 7], [3, 3], [7, 3], [7, 0], [0, 0], [0, 3], [4, 7], [0, 7]],
    '_': [[0, 0], [0, 1], [8, 1], [8, 0]],
};

// We map between the character and the internal name.
var TTF_NAME_MAP = { _: 'underscore' };


var gGlyph = 'A';
var gPoints = gGlyphMap[gGlyph];
var gPreviewText = 'HAMBURGEVONS';

var STATE_NORMAL = 'normal';
var STATE_DRAGGING = 'dragging';
var gState = STATE_NORMAL;
var OFFSET_X = 5 * SCREEN_SCALE;
var OFFSET_Y = 10 * SCREEN_SCALE;

var LETTER_WIDTH = 8 * SCREEN_SCALE;
var LETTER_HEIGHT = 10 * SCREEN_SCALE;

var gPointIndex = -1;
var POINT_DELTA = 10;

var canvas = document.getElementById('c');
var r = canvas.getBoundingClientRect();
canvas.width = r.width;
canvas.height = r.height;
var ctx = canvas.getContext('2d');
ctx.scale(1.0, -1.0);
ctx.translate(0, -canvas.height);

function findPointIndex(mx, my) {
    for (var i = gPoints.length - 1; i >= 0; i--) {
        var x = gPoints[i][0] * SCREEN_SCALE;
        var y = gPoints[i][1] * SCREEN_SCALE;
        if (Math.abs(mx - x) < POINT_DELTA && Math.abs(my - y) < POINT_DELTA) {
            return i;
        }
    }
    return -1;
}

// Execute all moveTo / lineTo instructions but don't fill / stroke the path yet.
// Note that the caller has to call beginPath.
function addGlyphPath(ctx, points, x, y, scale) {
    var x1 = x + points[0][0] * scale;
    var y1 = y + points[0][1] * scale;
    ctx.moveTo(x1, y1);

    var x2;
    var y2;

    for (var i = 1; i < points.length; i++) {
        x2 = x + points[i][0] * scale;
        y2 = y + points[i][1] * scale;
        ctx.lineTo(x2, y2);
    }
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Draw grid
    for (var x = 0; x < canvas.width; x += SCREEN_SCALE) {
        ctx.moveTo(x - 0.5, 0);
        ctx.lineTo(x - 0.5, canvas.height);
    }
    for (var y = 0; y < canvas.height; y += SCREEN_SCALE) {
        ctx.moveTo(0, y - 0.5);
        ctx.lineTo(canvas.width, y - 0.5);
    }
    ctx.lineWidth = 0.5;
    ctx.strokeStyle = '#333366';
    ctx.stroke();

    // Draw grid bounds
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(OFFSET_X, 0);
    ctx.lineTo(OFFSET_X, canvas.height);
    ctx.moveTo(0, OFFSET_Y);
    ctx.lineTo(canvas.width, OFFSET_Y);

    var advanceWidth = (getGlyphWidth(gPoints) + 1) * SCREEN_SCALE;
    ctx.moveTo(OFFSET_X + advanceWidth, 0);
    ctx.lineTo(OFFSET_X + advanceWidth, canvas.height);
    ctx.moveTo(0, OFFSET_Y + LETTER_HEIGHT);
    ctx.lineTo(canvas.width, OFFSET_Y + LETTER_HEIGHT);
    ctx.stroke();

    // Draw letter
    ctx.beginPath();
    addGlyphPath(ctx, gPoints, OFFSET_X, OFFSET_Y, SCREEN_SCALE);
    ctx.lineWidth = 5;
    ctx.strokeStyle = '#669';
    ctx.stroke();

    // Draw closing segment differently.
    ctx.beginPath();
    var firstPoint = gPoints[0];
    var lastPoint = gPoints[gPoints.length - 1];
    ctx.moveTo(OFFSET_X + lastPoint[0] * SCREEN_SCALE, OFFSET_Y + lastPoint[1] * SCREEN_SCALE);
    ctx.lineTo(OFFSET_X + firstPoint[0] * SCREEN_SCALE, OFFSET_Y + firstPoint[1] * SCREEN_SCALE);
    ctx.lineWidth = 2;
    ctx.setLineDash([5, 5]);
    ctx.strokeStyle = '#88b';
    ctx.stroke();
    ctx.setLineDash([]);

    // Draw small dots where the points are.
    for (var i = 0; i < gPoints.length; i++) {
        var x = OFFSET_X + gPoints[i][0] * SCREEN_SCALE;
        var y = OFFSET_Y + gPoints[i][1] * SCREEN_SCALE;
        ctx.fillStyle = '#cde';
        ctx.fillRect(x - 1, y - 1, 2, 2);
    }

    // Draw selected point
    if (gPointIndex >= 0) {
        var x = OFFSET_X + gPoints[gPointIndex][0] * SCREEN_SCALE;
        var y = OFFSET_Y + gPoints[gPointIndex][1] * SCREEN_SCALE;
        ctx.fillStyle = '#6666ff';
        ctx.fillRect(x - 6, y - 6, 12, 12);
    }

    drawPreview();
}

function drawPreview() {
    var c = document.getElementById('preview');
    var r = c.getBoundingClientRect();
    c.width = r.width;
    c.height = r.height;
    var ctx = c.getContext('2d');
    ctx.save();
    ctx.scale(1.0, -1.0);
    ctx.translate(0, -c.height);

    var x = 10;
    var y = 10;
    var previewScale = 8;
    ctx.beginPath();
    for (var i = 0; i < gPreviewText.length; i++) {
        var c = gPreviewText[i];
        c = c.toUpperCase();
        var points = gGlyphMap[c];
        if (points) {
            addGlyphPath(ctx, points, x, y, previewScale);
            ctx.closePath();
            x += (getGlyphWidth(points) + 1) * previewScale;
        } else {
            x += 7 * previewScale;
        }
    }
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#669';
    ctx.stroke();
    // ctx.fillStyle = 'rgb(215, 241, 255)';
    // ctx.fill();
    ctx.restore();
}

function getMousePosition(e) {
    var r = canvas.getBoundingClientRect();
    return {
        x: e.clientX - r.left,
        y: e.clientY - r.top
    };
}

function onMouseMove(e) {
    var pos = getMousePosition(e);
    var mx = pos.x - OFFSET_X;
    var my = (canvas.height - pos.y) - OFFSET_Y;
    if (gState === STATE_NORMAL) {
        gPointIndex = findPointIndex(mx, my);
    } else if (gState === STATE_DRAGGING) {
        var newX = Math.round(mx / SCREEN_SCALE);
        var newY = Math.round(my / SCREEN_SCALE);
        gPoints[gPointIndex] = [newX, newY];
    }
    //ctx.fillRect(mx-2, my-2, 4, 4);
    draw();
    drawGlyphList();
}

function onMouseDown(e) {
    if (gPointIndex >= 0) {
        if (e.shiftKey) {
            gPoints.splice(gPointIndex, 1);
            gPointIndex = -1;
            draw();
        } else {
            gState = STATE_DRAGGING;
        }
    }
}

function onMouseUp(e) {
    gState = STATE_NORMAL;
    dumpGlyphMap();
}

function dumpGlyphMap() {
    var code = document.getElementById('code');
    var json = 'var gGlyphMap = {\n';
    var characters = Object.keys(gGlyphMap);
    for (var i = 0; i < characters.length; i++) {
        var c = characters[i];
        var glyph = gGlyphMap[c];
        json += '  \'' + c + '\': ' + JSON.stringify(glyph) + ',\n';
    }
    json += '}\n';

    code.value = json;
}

// Look at all X values in the glyph and find the widest one.
function getGlyphWidth(points) {
    return points.reduce(function (w, b) {
        return Math.max(w, b[0]);
    }, 0);
}

function downloadFont() {
    // Convert the glyphs to an opentype font.
    var otGlyphs = [];
    otGlyphs.push(new opentype.Glyph({
        name: '.notdef',
        unicode: 0,
        path: new opentype.Path(),
        advanceWidth: 0,
    }));
    otGlyphs.push(new opentype.Glyph({
        name: 'space',
        unicode: 32,
        path: new opentype.Path(),
        advanceWidth: 7 * TTF_SCALE
    }));
    var keys = Object.keys(gGlyphMap);
    for (var i = 0; i < keys.length; i += 1) {
        var c = keys[i];
        // Do a conversion from the character name to the correct TrueType name.
        var ttfName = TTF_NAME_MAP[c] || c;
        var points = gGlyphMap[c];
        var p = new opentype.Path();
        // Remember the width of the character, to set the advanceWidth.
        var w = 0;
        for (var j = 0; j < points.length; j++) {
            var x = points[j][0];
            var y = points[j][1];
            if (j === 0) {
                p.moveTo(x * TTF_SCALE, y * TTF_SCALE);
            } else {
                p.lineTo(x * TTF_SCALE, y * TTF_SCALE);
            }
            w = Math.max(w, x);
        }
        var newGlyph = new opentype.Glyph({
            name: ttfName,
            unicode: c.charCodeAt(0),
            advanceWidth: (w + 1) * TTF_SCALE,
            path: p
        });
        otGlyphs.push(newGlyph);
    }
    var oFont = new opentype.Font({
        familyName: 'Pyramid',
        styleName: 'Regular',
        unitsPerEm: 1000,
        ascender: 800,
        descender: -200,
        glyphs: otGlyphs
    });
    console.log(oFont);
    const a = document.getElementById("download-font");
    a.href = window.URL.createObjectURL(new Blob([oFont.toArrayBuffer()], {type: "font/otf"}));
}

function onAddPoint() {
    var newX = Math.floor(Math.random() * 8);
    var newY = Math.floor(Math.random() * 8);
    gPoints.push([newX, newY]);
    dumpGlyphMap();
    draw();
}

function onRemovePoint() {
    delete gPoints.pop();
    dumpGlyphMap();
    draw();
}

function packValues() {
    var s = '';
    for (var i = 0; i < arguments.length; i += 1) {
        var v = arguments[i];
        if (v >= 0 && i > 0) {
            s += ' ';
        }

        s += floatToString(v);
    }

    return s;
}

function toPathData(points, scale) {
    var d = '';
    for (var i = 0; i < points.length; i += 1) {
        if (i === 0) {
            d += 'M';
        } else {
            d += 'L';
        }
        d += points[i][0] * scale;
        d += ' ';
        d += (10 - points[i][1]) * scale;
    }
    d += 'Z';
    return d;
}

function onSelectGlyph(e) {
    var target = e.target;
    if (!target.getAttribute('data-character')) {
        target = target.parentNode;
    }
    gGlyph = target.getAttribute('data-character');
    gPoints = gGlyphMap[gGlyph];
    drawGlyphList();
    draw();
}

function drawGlyphList() {
    var container = document.getElementById('glyph-list');
    container.innerHTML = '';
    var characters = Object.keys(gGlyphMap);
    for (var i = 0; i < characters.length; i++) {
        var c = characters[i];
        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        var d = toPathData(gGlyphMap[c], 7);
        svg.innerHTML = '<path transform="translate(10, 10)" fill="rgb(26, 70, 102)" stroke-width="2" stroke="rgb(215, 241, 255)" d="' + d + '"/>';
        container.appendChild(svg);
        svg.setAttribute('data-character', c);
        if (gGlyph === c) {
            svg.classList.add('active');
        }
        svg.addEventListener('click', onSelectGlyph);
    }
}

function onChangePreviewText(e) {
    gPreviewText = e.target.value;
    drawPreview();
}

// Mouse move
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mouseup', onMouseUp);
document.getElementById('add-point').addEventListener('click', onAddPoint);
document.getElementById('remove-point').addEventListener('click', onRemovePoint);
document.getElementById('download-font').addEventListener('click', downloadFont);
document.getElementById('preview-text-field').addEventListener('input', onChangePreviewText);

drawGlyphList();
draw();
dumpGlyphMap();
</script>
